package com.mayfool.gitmanager.core.helper;

import com.mayfool.gitmanager.core.action.ActionInfo;
import com.mayfool.gitmanager.core.action.ActionManager;
import com.mayfool.gitmanager.core.action.ActionType;
import com.mayfool.gitmanager.core.entity.BranchCreateResult;
import com.mayfool.gitmanager.core.entity.BranchDeleteResult;
import com.mayfool.gitmanager.core.entity.CredentialsInfo;
import com.mayfool.gitmanager.core.enums.BranchCreateStatus;
import com.mayfool.gitmanager.core.enums.BranchDeleteStatus;
import com.mayfool.gitmanager.core.manager.CredentialsManager;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.eclipse.jgit.api.ListBranchCommand;

import java.util.ArrayList;

/**
 * Git 操作辅助类
 * 
 * @author zhou.xu
 * @date 2024-01-08 10:30:00
 * 
 * 该类提供了 Git 仓库操作的核心功能，包括：
 * 1. 克隆本地/远程仓库
 * 2. 批量切换分支
 * 3. 分支合并
 * 4. 凭证管理
 */
public class GitOperations {
    /**
     * 工作空间目录
     */
    private File workspace;

    /**
     * 构造函数
     * @param workspace 工作空间目录
     */
    public GitOperations(File workspace) {
        this.workspace = workspace;
    }

    /**
     * 从本地路径克隆项目
     * 
     * @param localPath 本地项目路径
     */
    public void cloneFromLocal(String localPath) {
        try {
            File sourceDir = new File(localPath);
            File destDir = new File(workspace, sourceDir.getName());
            if (destDir.exists()) {
                return;
            }
            FileUtils.copyDirectory(sourceDir, destDir);
            LogHelper.getInstance().append("成功克隆本地项目: ").append(localPath).append("\n");
        } catch (IOException e) {
            LogHelper.getInstance().append("本地克隆失败: ").append(e.getMessage()).append("\n");
        }
    }

    /**
     * 批量克隆远程仓库
     * 
     * @param repoUrls 远程仓库 URL 列表
     */
    public void cloneRepositories(List<String> repoUrls) {
        repoUrls.parallelStream().forEach(url -> {
            try {
                File repoDir = new File(workspace, getRepoNameFromUrl(url));
                try (Git git = Git.cloneRepository()
                        .setURI(url)
                        .setDirectory(repoDir)
                        .call()) {
                    LogHelper.getInstance().append("成功克隆仓库: ").append(url).append("\n");
                }
            } catch (GitAPIException e) {
                LogHelper.getInstance().append("克隆失败: ").append(url).append(" - ").append(e.getMessage()).append("\n");
            }
        });
    }

    /**
     * 为工作空间中的所有项目切换分支
     * 
     * @param branchName 目标分支名
     */
    public void switchBranchForAll(String branchName) {
        File[] projects = workspace.listFiles(File::isDirectory);
        if (projects == null) return;

        for (File project : projects) {
            if (".idea".equals(project.getName())) continue;
            switchBranch(branchName, project);
        }
    }

    /**
     * 为单个项目切换分支
     * 
     * @param branchName 目标分支名
     * @param project 项目目录
     */
    private void switchBranch(String branchName, File project) {
        try (Repository repo = new FileRepositoryBuilder()
                .setGitDir(new File(project, ".git"))
                .build()) {
            CredentialsProvider credentialsProvider = getCredentialsProvider(project, repo);

            try (Git git = new Git(repo)) {
                git.fetch()
                        .setCredentialsProvider(credentialsProvider)
                        .call();

                // 1. 检查未提交变更
                Status status = git.status().call();
                if (!status.isClean()) {
                    LogHelper.getInstance().append(project.getName())
                            .append(" 存在未提交变更，跳过切换").append("\n");
                    return;
                }

                // 2. 检查分支是否存在
                boolean branchExists = git.branchList().call().stream()
                        .anyMatch(ref -> ref.getName().endsWith(branchName));

                // 3. 检查远程分支是否存在
                boolean remoteBranchExists = git.lsRemote()
                        .setCredentialsProvider(credentialsProvider)
                        .setHeads(true)
                        .call().stream()
                        .anyMatch(ref -> ref.getName().endsWith("/" + branchName));

                if (!branchExists) {
                    if (remoteBranchExists) {
                        // 检出远程分支到本地
                        git.checkout()
                                .setCreateBranch(true)
                                .setName(branchName)
                                .setStartPoint("origin/" + branchName)
                                .call();
                        LogHelper.getInstance().append(project.getName())
                                .append(" 成功创建并切换分支: ").append(branchName).append("\n");
                    } else {
                        LogHelper.getInstance().append(project.getName())
                                .append(" 分支不存在: ").append(branchName).append("\n");
                    }
                } else {
                    // 正常切换分支
                    git.checkout()
                            .setName(branchName)
                            .call();
                    LogHelper.getInstance().append(project.getName())
                            .append(" 成功切换分支: ").append(branchName).append("\n");
                }

            } catch (GitAPIException e) {
                LogHelper.getInstance().append(project.getName())
                        .append(" 切换失败: ").append(e.getMessage()).append("\n");
            }
        } catch (IOException e) {
            LogHelper.getInstance().append(project.getName())
                    .append(" 切换失败: ").append(e.getMessage()).append("\n");
        }
    }

    /**
     * 获取仓库的凭证提供者
     * 
     * @param project 项目目录
     * @param repo Git 仓库对象
     * @return 凭证提供者，如果未配置则返回 null
     */
    private static CredentialsProvider getCredentialsProvider(File project, Repository repo) {
        CredentialsInfo credentialsInfo = null;
        String remoteUrl = getRemoteUrl(repo);
        if (StringUtils.isBlank(remoteUrl)) {
            LogHelper.getInstance().append(project.getName())
                    .append(" 未配置远程仓库地址").append("\n");
            return null;
        }
        credentialsInfo = CredentialsManager.getCredentialsInfo(remoteUrl);
        if (credentialsInfo == null) {
            LogHelper.getInstance().append(project.getName())
                    .append(" 未配置远程仓库的用户名和密码，跳过切换").append("\n");
            ActionManager.fire(new ActionInfo(ActionType.SET_CREDENTIALS, remoteUrl));
            credentialsInfo = CredentialsManager.getCredentialsInfo(remoteUrl);
            if (credentialsInfo == null) {
                return null;
            }
        }
        String username = credentialsInfo.getUsername();
        String password = credentialsInfo.getPassword();
        return new UsernamePasswordCredentialsProvider(username, password);
    }

    /**
     * 获取仓库的远程 URL
     * 
     * @param repo Git 仓库对象
     * @return 远程仓库 URL，如果未配置则返回 null
     */
    private static String getRemoteUrl(Repository repo) {
        StoredConfig config = repo.getConfig();
        Set<String> remotes = config.getSubsections("remote");

        if (remotes.isEmpty()) {
            return null;
        }

        for (String remote : remotes) {
            return config.getString("remote", remote, "url");
        }
        return null;
    }

    /**
     * 合并指定分支到当前分支
     * 
     * @param sourceBranch 源分支名称
     */
    public void mergeBranches(String sourceBranch) {
        File[] projects = workspace.listFiles(File::isDirectory);
        if (projects == null) return;

        for (File project : projects) {
            try (Repository repo = new FileRepositoryBuilder()
                    .setGitDir(new File(project, ".git"))
                    .build()) {
                CredentialsProvider credentialsProvider = getCredentialsProvider(project, repo);
                
                try (Git git = new Git(repo)) {
                    // 检查未提交变更
                    Status status = git.status().call();
                    if (!status.isClean()) {
                        LogHelper.getInstance().append(project.getName())
                                .append(" 存在未提交变更，跳过合并").append("\n");
                        continue;
                    }

                    // 更新仓库
                    git.fetch()
                        .setCredentialsProvider(credentialsProvider)
                        .call();
                    git.pull()
                        .setCredentialsProvider(credentialsProvider)
                        .call();

                    // 优先合并远程分支
                    String remoteBranch = "origin/" + sourceBranch;
                    boolean remoteExists = git.lsRemote()
                            .setCredentialsProvider(credentialsProvider)
                            .setHeads(true)
                            .call().stream()
                            .anyMatch(ref -> ref.getName().endsWith("/" + sourceBranch));

                    if (remoteExists) {
                        git.merge()
                            .include(repo.findRef(remoteBranch))
                            .call();
                    } else {
                        git.merge()
                            .include(repo.findRef(sourceBranch))
                            .call();
                    }

                    LogHelper.getInstance().append(project.getName()).append(" 合并成功: ").append(
                        remoteExists ? remoteBranch : sourceBranch
                    ).append("\n");
                }
            } catch (IOException | GitAPIException e) {
                LogHelper.getInstance().append(project.getName()).append(" 合并失败: ").append(e.getMessage()).append("\n");
            }
        }
    }

    /**
     * 从仓库 URL 中提取仓库名称
     * 
     * @param url 仓库 URL
     * @return 仓库名称
     */
    private String getRepoNameFromUrl(String url) {
        return url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf(".git"));
    }

    /**
     * 检查分支状态
     * @param projectPath 项目路径
     * @param branchName 分支名称
     * @return 分支删除结果
     * @author zhou.xu
     * @date 2024-03-18 12:45:00
     */
    public BranchDeleteResult checkBranchStatus(String projectPath, String branchName) {
        try {
            Git git = Git.open(new File(projectPath));
            
            // 检查本地分支是否存在
            List<Ref> localBranches = git.branchList().call();
            boolean localExists = localBranches.stream()
                .anyMatch(ref -> ref.getName().equals("refs/heads/" + branchName));
                
            if (!localExists) {
                return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.BRANCH_NOT_FOUND);
            }
            
            // 检查是否是当前分支
            String currentBranch = git.getRepository().getBranch();
            if (branchName.equals(currentBranch)) {
                return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.CURRENT_BRANCH);
            }
            
            // 检查远程分支
            List<Ref> remoteBranches = git.branchList()
                .setListMode(ListBranchCommand.ListMode.REMOTE)
                .call();
            boolean remoteExists = remoteBranches.stream()
                .anyMatch(ref -> ref.getName().equals("refs/remotes/origin/" + branchName));
            
            BranchDeleteResult result = new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.SUCCESS);
            result.setHasRemoteBranch(remoteExists);
            return result;
            
        } catch (Exception e) {
            LogHelper.getInstance().error("检查分支状态失败: " + e.getMessage(), e);
            return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.ERROR, e.getMessage());
        }
    }
    
    /**
     * 删除本地分支
     * @param projectPath 项目路径
     * @param branchName 分支名称
     * @return 分支删除结果
     * @author zhou.xu
     * @date 2024-03-18 12:45:00
     */
    public BranchDeleteResult deleteLocalBranch(String projectPath, String branchName) {
        try {
            Git git = Git.open(new File(projectPath));
            
            // 删除本地分支
            git.branchDelete()
                .setBranchNames(branchName)
                .setForce(false)  // 不强制删除，确保分支已合并
                .call();
                
            return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.SUCCESS);
            
        } catch (Exception e) {
            LogHelper.getInstance().error("删除本地分支失败: " + e.getMessage(), e);
            return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.ERROR, e.getMessage());
        }
    }
    
    /**
     * 删除远程分支
     * @param projectPath 项目路径
     * @param branchName 分支名称
     * @return 分支删除结果
     * @author zhou.xu
     * @date 2024-03-18 12:45:00
     */
    public BranchDeleteResult deleteRemoteBranch(String projectPath, String branchName) {
        try {
            File gitDir = new File(projectPath, ".git");
            if (!gitDir.exists()) {
                return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.ERROR, "项目不是Git仓库");
            }

            Repository repo = new FileRepositoryBuilder()
                .setGitDir(gitDir)
                .build();
            try {
                // 获取凭证
                CredentialsProvider credentialsProvider = getCredentialsProvider(new File(projectPath), repo);
                if (credentialsProvider == null) {
                    return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.MISSING_CREDENTIALS, "未配置Git凭证");
                }

                // 检查远程分支是否存在
                Git git = new Git(repo);
                try {
                    Collection<Ref> remoteBranches = git.lsRemote()
                        .setCredentialsProvider(credentialsProvider)
                        .setHeads(true)
                        .call();
                    boolean remoteExists = remoteBranches.stream()
                        .anyMatch(ref -> ref.getName().endsWith("/" + branchName));
                    
                    if (!remoteExists) {
                        return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.ERROR, "远程分支不存在");
                    }

                    // 删除远程分支
                    git.push()
                        .setCredentialsProvider(credentialsProvider)
                        .setRemote("origin")
                        .setRefSpecs(new RefSpec(":" + "refs/heads/" + branchName))
                        .call();

                    return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.SUCCESS);
                } finally {
                    git.close();
                }
            } finally {
                repo.close();
            }
        } catch (Exception e) {
            LogHelper.getInstance().error("删除远程分支失败: " + e.getMessage(), e);
            return new BranchDeleteResult(projectPath, branchName, BranchDeleteStatus.ERROR, e.getMessage());
        }
    }

    /**
     * 创建新分支
     * 
     * @param projectPath 项目路径
     * @param newBranchName 新分支名称
     * @param startPoint 起始点（分支名或commit id），如果为null则从当前分支创建
     * @param pushToRemote 是否推送到远程仓库
     * @return 操作结果
     * @author zhou.xu
     * @date 2024-03-18 15:00:00
     */
    public BranchCreateResult createBranch(String projectPath, String newBranchName, String startPoint, boolean pushToRemote) {
        try {
            Git git = Git.open(new File(projectPath));
            
            // 检查分支是否已存在
            List<Ref> localBranches = git.branchList().call();
            boolean exists = localBranches.stream()
                .anyMatch(ref -> ref.getName().equals("refs/heads/" + newBranchName));
            
            if (exists) {
                return new BranchCreateResult(projectPath, newBranchName, BranchCreateStatus.BRANCH_EXISTS);
            }
            
            // 检查远程分支是否存在
            List<Ref> remoteBranches = git.branchList()
                .setListMode(ListBranchCommand.ListMode.REMOTE)
                .call();
            boolean remoteExists = remoteBranches.stream()
                .anyMatch(ref -> ref.getName().equals("refs/remotes/origin/" + newBranchName));
            
            if (remoteExists) {
                return new BranchCreateResult(projectPath, newBranchName, BranchCreateStatus.REMOTE_EXISTS);
            }
            
            // 创建新分支
            if (startPoint != null) {
                git.checkout()
                    .setCreateBranch(true)
                    .setName(newBranchName)
                    .setStartPoint(startPoint)
                    .call();
            } else {
                git.checkout()
                    .setCreateBranch(true)
                    .setName(newBranchName)
                    .call();
            }
            
            LogHelper.getInstance().info(String.format("在项目 %s 中创建分支: %s", projectPath, newBranchName));
            
            // 如果需要推送到远程
            if (pushToRemote) {
                CredentialsProvider credentialsProvider = getCredentialsProvider(new File(projectPath), git.getRepository());
                if (credentialsProvider == null) {
                    return new BranchCreateResult(projectPath, newBranchName, BranchCreateStatus.NO_CREDENTIALS);
                }
                
                git.push()
                    .setCredentialsProvider(credentialsProvider)
                    .setRemote("origin")
                    .setRefSpecs(new RefSpec(newBranchName + ":" + newBranchName))
                    .call();
                
                LogHelper.getInstance().info(String.format("推送分支 %s 到远程仓库", newBranchName));
            }
            
            return new BranchCreateResult(projectPath, newBranchName, BranchCreateStatus.SUCCESS);
            
        } catch (Exception e) {
            LogHelper.getInstance().error("创建分支失败: " + e.getMessage(), e);
            return new BranchCreateResult(projectPath, newBranchName, BranchCreateStatus.ERROR, e.getMessage());
        }
    }

    /**
     * 为工作空间中的所有项目创建分支
     * 
     * @param newBranchName 新分支名称
     * @param startPoint 起始点（分支名或commit id），如果为null则从当前分支创建
     * @param pushToRemote 是否推送到远程仓库
     * @return 操作结果列表
     * @author zhou.xu
     * @date 2024-03-18 15:00:00
     */
    public List<BranchCreateResult> createBranchForAll(String newBranchName, String startPoint, boolean pushToRemote) {
        List<BranchCreateResult> results = new ArrayList<>();
        File[] projects = workspace.listFiles(File::isDirectory);
        
        if (projects == null) {
            return results;
        }

        for (File project : projects) {
            if (".idea".equals(project.getName())) continue;
            
            BranchCreateResult result = createBranch(
                project.getAbsolutePath(),
                newBranchName,
                startPoint,
                pushToRemote
            );
            results.add(result);
        }
        
        return results;
    }
}